
package todolist.logic;

import java.util.regex.Pattern;
import javax.swing.JOptionPane;
import todolist.entity.SortDirectionType;
import todolist.entity.TaskFieldType;
import todolist.logic.parser.*;
import todolist.logic.reminder.ReminderController;
import todolist.logic.task.TaskController;
import todolist.personalization.CommandCustomizer;
import todolist.personalization.ErrorMessages;
import todolist.personalization.TaskFieldCustomizer;
import todolist.storage.Export;
import todolist.storage.Import;
import todolist.util.ConfigFileIO;
import todolist.util.Email;
import todolist.util.Utils;

/**
 * Top level Controller facade, Singleton class. View refreshes its display from here too.
 * @author You Li
 * @author Ivan (10%)
 */
public class MainController {
	
	// handle singleton instance

	private static MainController _objMC;
	
	public static MainController getInstance() {
		if (_objMC == null)
			_objMC = new MainController();
		return _objMC;
	}
	
	// member attributes
	
	private String _strApplicationProperties;
	private String _strNavigation;
	private String _strDisplay;
	private String _strCommand;
	private String _strFeedback;
	
	private MainController() {
		_strApplicationProperties = "";
		_strNavigation = "";
		_strDisplay = "";
		_strCommand = "";
		_strFeedback = "";
		
		TaskController objTC = TaskController.getInstance();
		ReminderController objRC = ReminderController.getInstance();
		Export objExport = Export.getInstance();
		Import objImport = Import.getInstance();
		
		// for controller --> (notify observer)
		objTC.addTaskChangedListener(objExport);
		objTC.addReminderTaskChangedListener(objRC);
		objRC.addReminderExpiredListener(objTC);
		objImport.addDocumentDeletedListener(objExport);
		objImport.addTaskImportedListener(objTC);
	}

	/**
	 * Initialises data before View loads GUI.
	 */
	public void init() {
		TaskController objTC = TaskController.getInstance();
		ConfigFileIO objConfig = ConfigFileIO.getInstance();
		Import.getInstance().importTaskEntities();
		objTC.sort(TaskFieldType.TASK_FIELD_DT, SortDirectionType.SORT_ASC); // earliest due tasks first
		_strApplicationProperties = objConfig.toString();
		_strNavigation = generateNavMenu();
		_strDisplay = objTC.getListAllString();
		_strCommand = "";
		String strTodaysTasks = objTC.getTodaysTasksString();
		if (strTodaysTasks.equals(""))
			return; // if no tasks due today, skip sending email 
		String strEmailError = Email.getInstance().sendEmail("To-Do-List Manager Daily Overview", strTodaysTasks);
		if (strEmailError != null)
			JOptionPane.showMessageDialog(
				null, strEmailError, "Email", // parent, message, title, messageType, icon
				JOptionPane.WARNING_MESSAGE, null);
	}
	
	// command facilitators

	/**
	 * Attempts to parse, then execute a Command entered by user.
	 */
	public void executeCommand(String pstrCommand) {
		_strCommand = pstrCommand; // do not modify _strCommand this point on
		// validate command
		_strFeedback = validateCommand(pstrCommand);
		if (_strFeedback != null)
			return;
		CommandParser objCP = new CommandParser();
		CommandType enmCommandType = objCP.parseCommandType(pstrCommand);
		pstrCommand = Utils.discardNWordsFromString(pstrCommand, 1).trim();
		// execute appropriate command
		if (enmCommandType == CommandType.COMMAND_ADD)
			executeAdd(pstrCommand);
		else if (enmCommandType == CommandType.COMMAND_EDIT)
			executeEdit(pstrCommand);
		else if (enmCommandType == CommandType.COMMAND_DELETE)
			executeDelete(pstrCommand);
		else if (enmCommandType == CommandType.COMMAND_DETAILS)
			executeDetails(pstrCommand);
		else if (enmCommandType == CommandType.COMMAND_LIST)
			executeListAll();
		else if (enmCommandType == CommandType.COMMAND_SEARCH)
			executeSearch(pstrCommand);
		else if (enmCommandType == CommandType.COMMAND_SORT)
			executeSort(pstrCommand);
		else {
			_strFeedback = ErrorMessages.ERR_INVALID_COMMAND_NAME;
			return;
		}
	}
	
	// private command methods

	/**
	 * Adds a new Task to the top of Task List, refreshes View's Display. O(1).
	 * Ensure all operations here MUST be O(1), except for parsing which can be slower due to phrase matching.
	 */
	private void executeAdd(String pstrCommand) {
		// validate input format
		String[] arrCommand = pstrCommand.split(
			"\\s*" + Pattern.quote(TaskFieldCustomizer.getInstance().getInputFieldDelimiter()) + "\\s*");
		TaskController objTC = TaskController.getInstance();
		int intFieldIndex = 0;
		// validate Task Name
		String strTaskName = arrCommand[intFieldIndex++].trim();
		if (strTaskName.equals("")) { // user may have delimited after command
			if (arrCommand.length <= intFieldIndex) {
				_strFeedback = ErrorMessages.ERR_INVALID_COMMAND_PARSING;
				return;
			}
			strTaskName = arrCommand[intFieldIndex++].trim();
		}
		_strFeedback = objTC.validateTaskName(strTaskName);
		if (_strFeedback != null)
			return;
		// validate DT
		String strDateTime;
		if (arrCommand.length <= intFieldIndex)
			strDateTime = "";
		else
			strDateTime = arrCommand[intFieldIndex++].trim();
		_strFeedback = objTC.validateDT(strDateTime);
		if (_strFeedback != null)
			return;
		// validate Priority
		String strPriority;
		if (arrCommand.length <= intFieldIndex)
			strPriority = "";
		else
			strPriority = arrCommand[intFieldIndex++].trim();
		_strFeedback = objTC.validatePriority(strPriority);
		if (_strFeedback != null)
			return;
		// validate Reminder
		String strReminder;
		if (arrCommand.length <= intFieldIndex)
			strReminder = "";
		else
			strReminder = arrCommand[intFieldIndex++].trim();
		_strFeedback = objTC.validateReminder(strReminder);
		if (_strFeedback != null)
			return;
		// validate Comments
		String strComments;
		if (arrCommand.length <= intFieldIndex)
			strComments = "";
		else
			strComments = arrCommand[intFieldIndex++].trim();
		_strFeedback = objTC.validateTaskComments(strComments);
		if (_strFeedback != null)
			return;
		PriorityParser objPP = new PriorityParser();
		// parse RO
		ReminderOffsetParser objROP = new ReminderOffsetParser();
		// add task
		objTC.addTask(strTaskName, strDateTime,
			objPP.parsePriority(strPriority), objROP.parseReminderOffset(strReminder).getReminderOffset(),
			strComments);
		// update UI
		_strDisplay = objTC.getListAllString();
		_strCommand = "";
		_strFeedback = "Task added succesfully with task name: " + strTaskName;
	}

	/**
	 * Edits a Task and refreshes View's Display with new Task List. O(n).
	 */
	private void executeEdit(String pstrCommand) {
		// validate input format
		pstrCommand = removeDelimiters(pstrCommand);
		String[] arrCommand = pstrCommand.split("\\s+");
		if (arrCommand.length < 2) {
			_strFeedback = ErrorMessages.ERR_INVALID_COMMAND_PARSING;
			return;
		}
		// validate individual input
		TaskController objTC = TaskController.getInstance();
		String strTaskID = arrCommand[0].trim();
		String strEditField = arrCommand[1].trim();
		_strFeedback = objTC.validateExistingTaskID(strTaskID);
		if (_strFeedback != null)
			return;
		long lngTaskID = Long.parseLong(strTaskID);
		_strFeedback = validateEditField(strEditField);
		if (_strFeedback != null)
			return;
		// parse edit field
		EditFieldParser objEP = new EditFieldParser();
		TaskFieldType enmEditField = objEP.parseEditField(strEditField);
		pstrCommand = Utils.discardNWordsFromString(pstrCommand, 2); // discount taskID and edit field
		// validate new value, returning error message if any
		if (enmEditField == TaskFieldType.TASK_FIELD_TASKNAME) {
			_strFeedback = objTC.validateTaskName(pstrCommand);
			if (_strFeedback != null)
				return;
		} else if (enmEditField == TaskFieldType.TASK_FIELD_DT) {
			_strFeedback = objTC.validateDT(pstrCommand);
			if (_strFeedback != null)
				return;
		} else if (enmEditField == TaskFieldType.TASK_FIELD_PRIORITY) {
			_strFeedback = objTC.validatePriority(pstrCommand);
			if (_strFeedback != null)
				return;
		} else if (enmEditField == TaskFieldType.TASK_FIELD_REMINDER) {
			_strFeedback = objTC.validateReminder(pstrCommand);
			if (_strFeedback != null)
				return;
		} else if (enmEditField == TaskFieldType.TASK_FIELD_COMMENTS) {
			_strFeedback = objTC.validateTaskComments(pstrCommand);
			if (_strFeedback != null)
				return;
		} else {
			_strFeedback = ErrorMessages.ERR_INVALID_EDIT_FIELD;
			return;
		}
		// edit task
		objTC.editTask(lngTaskID, enmEditField, pstrCommand);
		// update UI
		_strDisplay = objTC.getListAllString();
		_strCommand = "";
		_strFeedback = "Successfully edited task with ID: " + lngTaskID;
	}

	/**
	 * Deletes task and refreshes View's Display with new Task List. O(n).
	 */
	private void executeDelete(String pstrCommand) {
		// validate input format
		pstrCommand = removeDelimiters(pstrCommand);
		String[] arrCommand = pstrCommand.split("\\s+");
		if (arrCommand.length != 1) {
			_strFeedback = ErrorMessages.ERR_INVALID_COMMAND_PARSING;
			return;
		}
		TaskController objTC = TaskController.getInstance();
		// validate individual input
		_strFeedback = objTC.validateExistingTaskID(pstrCommand);
		if (_strFeedback != null)
			return;
		long lngTaskID = Long.parseLong(pstrCommand.trim());
		// delete task
		objTC.deleteTask(lngTaskID);
		// update UI
		_strDisplay = objTC.getListAllString();
		_strCommand = "";
		_strFeedback = "Successfully deleted task with task ID: " + lngTaskID;
	}

	/**
	 * Refreshes View's Display with full details of one Task. O(n).
	 */
	private void executeDetails(String pstrCommand) {
		// validate input format
		pstrCommand = removeDelimiters(pstrCommand);
		String[] arrCommand = pstrCommand.split("\\s+");
		if (arrCommand.length != 1) {
			_strFeedback = ErrorMessages.ERR_INVALID_COMMAND_PARSING;
			return;
		}
		TaskController objTC = TaskController.getInstance();
		// validate individual input
		_strFeedback = objTC.validateExistingTaskID(pstrCommand);
		if (_strFeedback != null)
			return;
		long lngTaskID = Long.parseLong(pstrCommand.trim());
		// update UI, show task details
		_strDisplay = objTC.getShowDetailsString(lngTaskID);
		_strCommand = "";
		_strFeedback = "";
	}

	/**
	 * Refreshes View's Display with Task List. O(1).
	 */
	private void executeListAll() {
		// list all tasks
		TaskController objTC = TaskController.getInstance();
		String strListAll = objTC.getListAllString();
		// update UI
		_strDisplay = strListAll;
		_strCommand = "";
		_strFeedback = (strListAll.equals("") ? "No Tasks to display." : "");
	}

	/**
	 * Refreshes View's Display with Search results. O(n).
	 */
	private void executeSearch(String pstrCommand) {
		pstrCommand = removeDelimiters(pstrCommand);
		TaskController objTC = TaskController.getInstance();
		// search tasks
		String strSearchResults = objTC.getSearchResultsString(pstrCommand);
		// update UI
		_strDisplay = strSearchResults;
		_strCommand = "";
		_strFeedback = (strSearchResults.equals("") ? "No s" : "S") + "earch results found.";
	}

	/**
	 * Sorts Task list and refreshes View's Display. O(n log n).
	 */
	private void executeSort(String pstrCommand) {
		// validate input format
		pstrCommand = removeDelimiters(pstrCommand);
		String[] arrCommand = pstrCommand.split("\\s+");
		if (arrCommand.length != 2) {
			_strFeedback = ErrorMessages.ERR_INVALID_COMMAND_PARSING;
			return;
		}
		TaskController objTC = TaskController.getInstance();
		// validate individual input
		SortParser objSP = new SortParser();
		SortParseResult objResult = objSP.parseSort(pstrCommand);
		_strFeedback = objResult.getError();
		if (_strFeedback != null)
			return;
		// sort tasks
		objTC.sort(objResult.getTaskField(), objResult.getSortDirection());
		// update UI
		_strDisplay = objTC.getListAllString();
		_strCommand = "";
		_strFeedback = "";
	}
	
	// private validate methods

	/**
	 * Returns null if Command name is valid, or Error Message otherwise.
	 */
	private String validateCommand(String pstrCommand) {
		CommandParser objCP = new CommandParser();
		boolean blnCommandNotFound = (objCP.parseCommandType(pstrCommand) == null);
		if (blnCommandNotFound)
			return ErrorMessages.ERR_INVALID_COMMAND_NAME;
		return null;
	}

	/**
	 * Returns null if Edit field is valid, or Error Message otherwise.
	 */
	private String validateEditField(String pstrEditField) {
		EditFieldParser objEP = new EditFieldParser();
		boolean blnEditFieldNotFound = (objEP.parseEditField(pstrEditField) == null);
		if (blnEditFieldNotFound)
			return ErrorMessages.ERR_INVALID_EDIT_FIELD;
		return null;
	}
	
	// other private methods

	/**
	 * Removes Field Delimiters from input and trims it.
	 */	
	private String removeDelimiters(String pstrInput) {
		if (pstrInput == null)
			throw new IllegalArgumentException("MainController.removeDelimiter(): Input cannot be null.");
		return pstrInput.replaceAll( // replace delimiter and spaces on both sides with one space
			"\\s*" + Pattern.quote(TaskFieldCustomizer.getInstance().getInputFieldDelimiter()) + "\\s*",
			" ").trim();
	}
	
	/**
	 * Returns String of generated navigation menu text.
	 */	
	private String generateNavMenu() {
		return
			CommandCustomizer.getInstance().getDisplayAddCommand() +
			" <Name> || [Date] || [Priority] || [Reminder] || [Comments]" + Utils.getLineSeparator() +
			CommandCustomizer.getInstance().getDisplayEditCommand() +
			" <ID> <Field> <Value>" + Utils.getLineSeparator() +
			CommandCustomizer.getInstance().getDisplayDeleteCommand() +
			" <ID>" + Utils.getLineSeparator() +
			CommandCustomizer.getInstance().getDisplayDetailsCommand() +
			" <ID>" + Utils.getLineSeparator() +
			CommandCustomizer.getInstance().getDisplayListCommand() +
			Utils.getLineSeparator() +
			CommandCustomizer.getInstance().getDisplaySearchCommand() +
			" <Query>" + Utils.getLineSeparator() +
			CommandCustomizer.getInstance().getDisplaySortCommand() +
			" <Criteria> <Direction>" + Utils.getLineSeparator();
	}
	
	// getters
	
	public String getApplicationProperties() {
		return _strApplicationProperties;
	}
	
	public String getNavigation() {
		return _strNavigation;
	}
	
	public String getDisplay() {
		return _strDisplay;
	}
	
	public String getCommand() {
		return _strCommand;
	}
	
	public String getFeedback() {
		return _strFeedback;
	}
	
}
